package com.efeichong.common.utils;


import com.efeichong.common.exception.CustomException;
import com.efeichong.common.utils.json.JsonUtils;
import com.google.common.io.ByteStreams;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.GzipCompressingEntity;
import org.apache.http.client.entity.GzipDecompressingEntity;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.config.SocketConfig;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author : lxk
 * @Date : 2020/4/3
 * @Description :
 * @Builder httpclient工具类
 */
@Slf4j
public class HttpClientUtils {
    private static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, " +
            "like Gecko) Chrome/72.0.3626.119 Safari/537.36";
    private static PoolingHttpClientConnectionManager cm;

    static {
        cm = new PoolingHttpClientConnectionManager();
        // 整个连接池最大连接数
        cm.setMaxTotal(200);
        // 每路由最大连接数
        cm.setDefaultMaxPerRoute(5);
    }

    /**
     * 请求头信息
     */
    private Map<String, String> headers = new HashMap<>();
    /**
     * body参数/form表单参数
     */
    private Map<String, String> bodyParams;
    /**
     * 拼接在url上的参数
     */
    private Map<String, String> urlParams;
    /**
     * 字符集
     */
    private String charset = StandardCharsets.UTF_8.toString();
    /**
     * 设置超时时间
     */
    private int timeOut = 12 * 1000;
    /**
     * 设置代理
     */
    private HttpHost proxy;
    /**
     * 失败重试
     */
    private HttpRequestRetryHandler requestRetryHandler;
    /**
     * 对返回值解压缩
     */
    private boolean responseGzip = false;
    /**
     * 对请求进行压缩
     */
    private boolean requestGzip = false;

    public static HttpClientUtils create() {
        return new HttpClientUtils();
    }

    public HttpClientUtils setHeaders(Map<String, String> headers) {
        this.headers.putAll(headers);
        return this;
    }

    public HttpClientUtils addHeader(String key, String value) {
        this.headers.put(key, value);
        return this;
    }

    public HttpClientUtils setUrlParams(Map<String, String> params) {
        this.urlParams = params;
        return this;
    }

    public HttpClientUtils setBodyParams(Map<String, String> params) {
        this.bodyParams = params;
        return this;
    }

    public HttpClientUtils setCharset(String charset) {
        this.charset = charset;
        return this;
    }

    public HttpClientUtils setTimeOut(int timeOut) {
        this.timeOut = timeOut;
        return this;
    }

    public HttpClientUtils setProxy(HttpHost proxy) {
        this.proxy = proxy;
        return this;
    }

    public HttpClientUtils setResponseGzip(boolean responseGzip) {
        this.responseGzip = responseGzip;
        return this;
    }

    public HttpClientUtils setRequestGzip(boolean requestGzip) {
        this.requestGzip = requestGzip;
        return this;
    }

    public HttpClientUtils setRequestRetryHandler(HttpRequestRetryHandler requestRetryHandler) {
        this.requestRetryHandler = requestRetryHandler;
        return this;
    }


    /**
     * post请求传输json参数
     *
     * @param url
     * @param resultType
     * @return
     */
    public <T> T post(String url, Class<T> resultType) {
        CloseableHttpClient httpClient = initHttpClient();
        CloseableHttpResponse response = null;
        if (isNotEmpty(urlParams)) {
            url = concatParam(url);
        }
        HttpPost httpPost = new HttpPost(url);
        try {
            if (isNotEmpty(bodyParams)) {
                StringEntity entity = new StringEntity(JsonUtils.toJson(bodyParams), charset);
                entity.setContentEncoding(charset);
                entity.setContentType(ContentType.APPLICATION_JSON.getMimeType());
                if (requestGzip) {
                    httpPost.setEntity(new GzipCompressingEntity(entity));
                } else {
                    httpPost.setEntity(entity);
                }
            }
            if (isNotEmpty(headers)) {
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    httpPost.addHeader(entry.getKey(), entry.getValue());
                }
            }
            response = httpClient.execute(httpPost);
            checkResponse(response);
            return buildResponse(response, resultType);
        } catch (Exception e) {
            log.error("http请求异常:", e);
            return null;
        } finally {
            closeStream(httpPost, response);
        }
    }


    /**
     * form
     *
     * @param url
     * @param resultType
     * @return
     */
    public <T> T postForm(String url, Class<T> resultType) {
        CloseableHttpClient httpClient = initHttpClient();
        CloseableHttpResponse response = null;
        if (isNotEmpty(urlParams)) {
            url = concatParam(url);
        }
        HttpPost httpPost = new HttpPost(url);
        try {
            if (isNotEmpty(bodyParams)) {
                List<NameValuePair> list = new ArrayList<>();
                for (Map.Entry<String, String> entry : bodyParams.entrySet()) {
                    list.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
                }
                StringEntity entity = new UrlEncodedFormEntity(list, charset);
                entity.setContentEncoding(charset);
                entity.setContentType(ContentType.APPLICATION_FORM_URLENCODED.getMimeType());
                if (requestGzip) {
                    httpPost.setEntity(new GzipCompressingEntity(entity));
                } else {
                    httpPost.setEntity(entity);
                }
            }
            if (isNotEmpty(headers)) {
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    httpPost.addHeader(entry.getKey(), entry.getValue());
                }
            }
            response = httpClient.execute(httpPost);
            checkResponse(response);
            return buildResponse(response, resultType);
        } catch (Exception e) {
            log.error("http请求异常:", e);
            return null;
        } finally {
            closeStream(httpPost, response);
        }
    }

    /**
     * get 请求
     *
     * @param url        请求地址
     * @param resultType 返回类型
     * @param <T>
     * @return
     */
    public <T> T get(String url, Class<T> resultType) {
        CloseableHttpClient httpClient = initHttpClient();
        CloseableHttpResponse response = null;
        if (isNotEmpty(urlParams)) {
            url = concatParam(url);
        }
        HttpGet httpGet = new HttpGet(url);
        try {
            if (isNotEmpty(headers)) {
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    httpGet.setHeader(entry.getKey(), entry.getValue());
                }
            }
            response = httpClient.execute(httpGet);
            checkResponse(response);
            return buildResponse(response, resultType);
        } catch (Exception e) {
            log.error("http请求异常:", e);
            return null;
        } finally {
            closeStream(httpGet, response);
        }
    }

    /**
     * 校验返回是否为空
     *
     * @param response
     */
    private void checkResponse(CloseableHttpResponse response) {
        if (response == null) {
            throw new CustomException("response empty");
        }
        if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
            throw new CustomException("response failed:" + response.getStatusLine().getStatusCode());
        }
    }

    /**
     * 构建返回结果
     *
     * @param response
     * @param resultType
     * @param <T>
     * @return
     * @throws IOException
     */
    @SneakyThrows
    private <T> T buildResponse(CloseableHttpResponse response, Class<T> resultType) {
        if (resultType.isAssignableFrom(String.class)) {
            if (responseGzip) {
                GzipDecompressingEntity decompressingEntity = new GzipDecompressingEntity(response.getEntity());
                return (T) new String(ByteStreams.toByteArray(decompressingEntity.getContent()), charset);
            }
            return (T) EntityUtils.toString(response.getEntity(), charset);
        } else if (resultType.isAssignableFrom(InputStream.class)) {
            if (responseGzip) {
                GzipDecompressingEntity decompressingEntity = new GzipDecompressingEntity(response.getEntity());
                return (T) decompressingEntity.getContent();
            }
            return (T) response.getEntity().getContent();
        } else if (resultType.isAssignableFrom(Document.class)) {
            if (responseGzip) {
                GzipDecompressingEntity decompressingEntity = new GzipDecompressingEntity(response.getEntity());
                return (T) DocumentHelper.parseText(new String(ByteStreams.toByteArray(decompressingEntity.getContent()), charset));
            }
            return (T) DocumentHelper.parseText(EntityUtils.toString(response.getEntity(), charset));
        } else {
            if (responseGzip) {
                GzipDecompressingEntity decompressingEntity = new GzipDecompressingEntity(response.getEntity());
                return JsonUtils.toObj(new String(ByteStreams.toByteArray(decompressingEntity.getContent()), charset), resultType);
            }
            return JsonUtils.toObj(EntityUtils.toString(response.getEntity(), charset), resultType);
        }
    }

    /**
     * 关闭流
     *
     * @param request
     * @param response
     */
    private void closeStream(HttpRequestBase request, CloseableHttpResponse response) {
        try {
            if (response != null) {
                response.close();
            }
            if (request != null) {
                request.releaseConnection();
            }
        } catch (IOException e) {
            log.warn("流关闭失败!");
        }
    }

    /**
     * 在url上拼接参数
     *
     * @param url
     * @return
     */
    private String concatParam(String url) {
        StringBuilder builder = new StringBuilder(url).append("?");
        for (Map.Entry<String, String> entry : urlParams.entrySet()) {
            builder.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
        }
        if (builder.lastIndexOf("&") == builder.length() - 1) {
            return builder.substring(0, builder.length() - 1);
        }
        return builder.toString();
    }

    private CloseableHttpClient initHttpClient() {
        SocketConfig socketConfig = SocketConfig.custom()
                .setSoTimeout(timeOut).build();
        RequestConfig requestConfig = RequestConfig.custom()
                .setSocketTimeout(timeOut)
                .setConnectTimeout(timeOut)
                .setSocketTimeout(timeOut).build();
        HttpClientBuilder httpClientBuilder = HttpClients.custom()
                .setConnectionManager(cm)
                .setDefaultRequestConfig(requestConfig)
                .setDefaultSocketConfig(socketConfig);
        if (proxy != null) {
            httpClientBuilder.setProxy(proxy);
        }
        httpClientBuilder.setUserAgent(USER_AGENT);
        if (requestRetryHandler != null) {
            httpClientBuilder.setRetryHandler(requestRetryHandler);
        }
        return httpClientBuilder.build();
    }

    private boolean isNotEmpty(Map<String, String> map) {
        return map != null && !map.isEmpty();
    }
}